#include <stdio.h>
#include <errno.h>
#include <sys/sem.h>
#include "sem.h"
#include "sig.h"
#include "../error.h"

/*--------------------------------------------------------------------------*/

/* intr tells if it can be interrupted by a signal or not */
static int sem(int semId, int semNum, int semOp, int flags, int intr);

static void doNothing(int s);

/*--------------------------------------------------------------------------*/

extern int errno;

static char errorMsg[255];

static int mainTimeout;

/*--------------------------------------------------------------------------*/

/* Returns the semid or less than zero if error */
int
createSem(int key, int count, int perms)
{

	union semun
	{
		int val;
		struct semid_ds *buf;
		unsigned short *array;
	} ctl_arg;
	int semId;
	int ans;
	int error;
	
	if( count <= 0 )
	{	
		fprintf(stderr,"sem: Creating, invalid parameter. count = %d\n",count);
		return ERROR_PARAMS;
	}
	
	ans = semget(key, count, perms|IPC_CREAT|IPC_EXCL );
	if( ans == -1 )
	{
		if( errno == EEXIST )
		{	
			sprintf(errorMsg,"sem: Creating set, key %d already exists.\n", key);
			perror(errorMsg);
			error = ERROR_EXIST;
		}
		else if( errno == EACCES )
		{	
			perror("sem: Creating set, acces denied, check semaphore permissions.\n");
			error = ERROR_ACCES;
		}
		else
		{
			perror("sem: Creating set, unexpected error.\n");
			error = ERROR_UNEX;			
		}
	}
	else
	{
		semId = ans;
		error = ans;
		/* Set initial value to 1 */
		ctl_arg.val = 1;
		while( count-- )
		{
/*			if( count == 1 )
				ctl_arg.val = 0;
			else
				ctl_arg.val = 1;
*/
			ans = semctl( semId, count, SETVAL, ctl_arg);
			if( ans == -1 )
			{
				perror("sem: Creating, error setting initial value of the sempahore set.\n" );
				if( delSem(semId) != ERROR_OK )
					perror("sem: Creating, error removing sempahore due to initialising failure.\n" );
				count = 0;
				error = ERROR_UNEX;
			}
		}
	}
	
	return error;
}

/*--------------------------------------------------------------------------*/

/* Returns the semid or less than zero if error */
int
openSem(int key, int count)
{

	int ans;
	
	ans = semget(key, count, 0 );
	if( ans == -1 )
	{
		if( errno == ENOENT )
		{	
			sprintf(errorMsg,"sem: Opening set, key does not exist. key = %d\n",key);
			perror(errorMsg);
			ans = ERROR_EXIST;
		}
		else if( errno == EACCES )
		{	
			perror("sem: Opening set, acces denied, check semaphore permissions.\n");
			ans = ERROR_ACCES;
		}
		else
		{
			perror("sem: Opening set, unexpected error.\n");
			ans = ERROR_UNEX;			
		}
	}

	return ans;		
}

/*--------------------------------------------------------------------------*/

int
delSem(int semId)
{
	int error = ERROR_OK;

	if( semctl(semId, 0, IPC_RMID, NULL) == -1)
	{
		if( errno == EACCES )
		{	
			perror("sem: Deleting set, acces denied, check semaphore permissions.\n");
			error = ERROR_ACCES;
		}
		else
		{
			perror("sem: Deleting set, unexpected error.\n");
			error = ERROR_UNEX;			
		}
	}
	
	return error;
}

/*--------------------------------------------------------------------------*/
static int
sem(int semId, int semNum, int semOp, int flags, int timeout )
{
	struct sembuf buf;
	int signal = 0;
	int error = ERROR_OK;

	/* Semaphore wait */
	buf.sem_num = semNum;
	buf.sem_op = semOp;
	/* Set the SEM_UNDO flag */
	/* If another instance is running it waits until it finishes */
	/* Else, use the IPC_NOWAIT flag */
	buf.sem_flg = flags;
	
	/* Set timer alarm */
	if( timeout != 0 )
	{
		mainTimeout = 0;
		if( setAlarm(timeout, doNothing) != ERROR_OK)
		{
			fprintf(stderr,"sem: Error setting timeout alarm.\n");
			return ERROR_UNEX;	
		}
	}
	
	do
	{
		signal = 0;
		if( semop(semId, &buf, 1) == -1 )
		{
			if( errno == EACCES )
			{	
				perror("sem: Operation, acces denied, check semaphore permissions.\n");
				error = ERROR_ACCES;
			}
			else if( errno == EINTR )
			{
				if( !timeout || !mainTimeout )
					signal = 1;
			}
			else if( errno == EAGAIN )
			{
				error = ERROR_NOWAIT;
			}
			else
			{
				perror("sem: Operation, unexpected error.\n");
				error = ERROR_UNEX;	
			}
		}
	}while( signal );

	if( timeout && mainTimeout)
	{	
		unsetAlarm();
		return ERROR_TIMEOUT;
	}
	else
		return error;
}

/*--------------------------------------------------------------------------*/

int
pSem(int semId, int semNum, int timeout)
{
	return sem(semId, semNum, -1, SEM_UNDO, timeout);
}

/*--------------------------------------------------------------------------*/

int
vSem(int semId, int semNum)
{
	return sem(semId, semNum, 1, SEM_UNDO, 0);
}

/*--------------------------------------------------------------------------*/

int
waitSem(int semId, int semNum, int timeout)
{
	return sem(semId, semNum, -2, 0, timeout);
}

/*--------------------------------------------------------------------------*/

int
readySem(int semId, int semNum)
{
	return sem(semId, semNum, 2, 0, 0);
}

/*--------------------------------------------------------------------------*/

int
pInstanceSem(int semId, int semNum)
{
	int ans;
	
	ans = sem(semId, semNum, -1, SEM_UNDO | IPC_NOWAIT , 0);
	if( ans == ERROR_NOWAIT )
		return ERROR_INST;
	else
		return ans;
}

/*--------------------------------------------------------------------------*/

int
vInstanceSem(int semId, int semNum)
{
	return sem(semId, semNum, 1, 0, 0);
}

/*--------------------------------------------------------------------------*/

static void
doNothing(int s)
{
	mainTimeout = 1;
}

/*--------------------------------------------------------------------------*/

